/**
 * laydate
 * 日期与时间组件
 */

import { layui } from '../core/layui.js';
import { lay } from '../core/lay.js';
import { i18n } from '../core/i18n.js';

// 模块名
var MOD_NAME = 'laydate';
var MOD_ID = 'lay-' + MOD_NAME + '-id'; // 已渲染过的索引标记名
var zhCN = 'zh-CN'; // 简体中文语言码
var YearBeforeMonthLocale = [
  'eu-ES',
  'ja-JP',
  'km-KH',
  'ko-KR',
  'pt-BR',
  'si-LK',
  'ms-MY',
  'ug-CN',
  'zh-CN',
  'zh-HK',
  'zh-TW',
]; // 年份在前的语言

function addSpaceBetweenChars(str) {
  if (typeof str !== 'string' || str.length <= 1) {
    return str;
  }

  var result = '';
  for (var i = 0; i < str.length - 1; i++) {
    var char = str[i];
    var nextChar = str[i + 1];
    result += char;

    // 判断当前字符和下一个字符的类型
    var isCharDigit = isDigit(char);
    var isNextCharDigit = isDigit(nextChar);

    // 在数字和非数字（非空格）之间添加空格
    if (
      (isCharDigit && !isNextCharDigit && nextChar !== ' ') || // 数字 → 非数字（非空格）
      (char !== ' ' && !isCharDigit && isNextCharDigit) // 非空格非数字 → 数字
    ) {
      result += ' ';
    }
  }
  result += str[str.length - 1]; // 添加最后一个字符
  return result;
}

function isDigit(char) {
  var code = char.charCodeAt(0);
  return code >= 48 && code <= 57; // '0' 到 '9' 的 ASCII 码范围
}

// 对象合并时，让数组覆盖，而非合并
function overwriteArray(objValue, srcValue) {
  // 数组覆盖而非合并
  if (Array.isArray(objValue) && Array.isArray(srcValue)) {
    return srcValue;
  }
}

// 外部调用
var laydate = {
  config: {
    weekStart: 0, // 默认周日一周的开始
  }, // 全局配置项

  // 设置全局项
  set: function (options) {
    var that = this;
    that.config = lay.extend({}, that.config, options, overwriteArray);
    return that;
  },

  // 主体 CSS 等待事件
  ready: function (callback) {
    var cssname = 'laydate';
    var path = 'modules/laydate.css?v=' + laydate.v;

    // 打包版直接执行回调函数
    if (layui['layui.all']) {
      typeof callback === 'function' && callback();
    } else {
      layui.addcss(path, callback, cssname);
    }

    return this;
  },
};

// 操作当前实例
var thisModule = function () {
  var that = this;
  var options = that.config;
  var id = options.id;

  thisModule.that[id] = that; // 记录当前实例对象

  return (that.inst = {
    // 提示框
    hint: function (content) {
      that.hint.call(that, content);
    },
    // 重载实例
    reload: function (options) {
      that.reload.call(that, options);
    },
    config: that.config,
  });
};

// 字符常量
var ELEM = '.layui-laydate';
var THIS = 'layui-this';
// var SHOW = 'layui-show';
// var HIDE = 'layui-hide';
var DISABLED = 'laydate-disabled';
var LIMIT_YEAR = [100, 200000];

var ELEM_STATIC = 'layui-laydate-static';
var ELEM_LIST = 'layui-laydate-list';
var ELEM_SELECTED = 'laydate-selected';
var ELEM_HINT = 'layui-laydate-hint';
var ELEM_DAY_NOW = 'laydate-day-now';
var ELEM_PREV = 'laydate-day-prev';
var ELEM_NEXT = 'laydate-day-next';
var ELEM_FOOTER = 'layui-laydate-footer';
var ELEM_SHORTCUT = 'layui-laydate-shortcut';
var ELEM_NOW = '.laydate-btns-now';
var ELEM_CONFIRM = '.laydate-btns-confirm';
var ELEM_TIME_TEXT = 'laydate-time-text';
var ELEM_TIME_BTN = 'laydate-btns-time';
var ELEM_PREVIEW = 'layui-laydate-preview';
var ELEM_MAIN = 'layui-laydate-main';
var ELEM_SHADE = 'layui-laydate-shade';

// 组件构造器
var Class = function (options) {
  var that = this;
  that.index = laydate.index = lay.autoIncrementer('laydate');
  that.config = lay.extend(
    {},
    that.config,
    laydate.config,
    options,
    overwriteArray,
  );

  // 若 elem 非唯一，则拆分为多个实例
  var elem = lay(options.elem || that.config.elem);
  if (elem.length > 1) {
    lay.each(elem, function () {
      laydate.render(
        lay.extend({}, that.config, {
          elem: this,
        }),
        overwriteArray,
      );
    });
    return that;
  }

  // 初始化属性
  options = lay.extend(that.config, lay.options(elem[0]), overwriteArray); // 继承节点上的属性

  // 更新 i18n 消息对象
  that.i18nMessages = that.getI18nMessages();

  // 处理日期面板顶部年月顺序
  // 这是一个变通的方法，因为 i18nMessages.monthBeforeYear 不存在
  if (typeof that.i18nMessages.monthBeforeYear !== 'boolean') {
    if (!window.Intl) {
      that.i18nMessages.monthBeforeYear = !(
        YearBeforeMonthLocale.indexOf(options.lang) > -1
      );
    } else {
      var formatter = new Intl.DateTimeFormat(options.lang, {
        year: 'numeric',
        month: 'short',
      });
      var parts = formatter.formatToParts(new Date(1970, 0));
      var order = [];
      parts.map(function (part) {
        if (part.type === 'year' || part.type === 'month') {
          order.push(part.type);
        }
      });
      that.i18nMessages.monthBeforeYear = order[0] === 'month';
    }
  }

  // 若重复执行 render，则视为 reload 处理
  if (elem[0] && elem.attr(MOD_ID)) {
    var newThat = thisModule.getThis(elem.attr(MOD_ID));
    if (!newThat) return;
    return newThat.reload(options);
  }

  // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
  options.id = 'id' in options ? options.id : elem.attr('id') || that.index;

  // 自增索引
  options.index = that.index;

  // 初始化
  laydate.ready(function () {
    that.init();
  });
};

// 日期格式字符
var dateType = 'yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s';

// 将日期格式字符转换为数组
thisModule.formatArr = function (format) {
  return (format || '').match(new RegExp(dateType + '|.', 'g')) || [];
};

/*
  组件操作
*/

// 是否闰年
Class.isLeapYear = function (year) {
  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
};

// 默认配置
Class.prototype.config = {
  type: 'date', // 控件类型，支持：year/month/date/time/datetime
  range: false, // 是否开启范围选择，即双控件
  format: 'yyyy-MM-dd', // 默认日期格式
  value: null, // 默认日期，支持传入new Date()，或者符合format参数设定的日期格式字符
  isInitValue: true, // 用于控制是否自动向元素填充初始值（需配合 value 参数使用）
  min: '1900-1-1', // 有效最小日期，年月日必须用“-”分割，时分秒必须用“:”分割。注意：它并不是遵循 format 设定的格式。
  max: '2099-12-31', // 有效最大日期，同上
  trigger: 'click', // 呼出控件的事件
  show: false, // 是否直接显示，如果设置 true，则默认直接显示控件
  showBottom: true, // 是否显示底部栏
  isPreview: true, // 是否显示值预览
  btns: ['clear', 'now', 'confirm'], // 右下角显示的按钮，会按照数组顺序排列
  // 为实现 lang 选项就近生效，去除此处的默认值，$t 设置了英文回退值
  lang: '', // 语言，只支持 cn/en，即中文和英文
  theme: 'default', // 主题
  position: null, // 控件定位方式定位, 默认absolute，支持：fixed/absolute/static
  calendar: false, // 是否开启公历重要节日，仅支持中文版
  mark: {}, // 日期备注，如重要事件或活动标记
  holidays: null, // 标注法定节假日或补假上班
  zIndex: null, // 控件层叠顺序
  done: null, // 控件选择完毕后的回调，点击清空/现在/确定也均会触发
  change: null, // 日期时间改变后的回调
  autoConfirm: true, // 是否自动确认（日期|年份|月份选择器非range下是否自动确认）
  shade: 0,
};

Class.prototype.getI18nMessages = function () {
  var that = this;
  var options = that.config;
  var locale = i18n.config.locale;

  // 纠正旧版「简体中文」语言码
  if (options.lang === 'cn') {
    options.lang = zhCN;
  } else if (!options.lang) {
    options.lang = i18n.config.locale;
  }
  locale = options.lang;

  return {
    months: i18n.$t('laydate.months', null, {
      locale: locale,
      default: [
        'Jan',
        'Feb',
        'Mar',
        'Apr',
        'May',
        'Jun',
        'Jul',
        'Aug',
        'Sep',
        'Oct',
        'Nov',
        'Dec',
      ],
    }),
    weeks: i18n.$t('laydate.weeks', null, {
      locale: locale,
      default: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
    }),
    time: i18n.$t('laydate.time', null, {
      locale: locale,
      default: ['Hour', 'Minute', 'Second'],
    }),
    literal: {
      year: i18n.$t('laydate.literal.year', null, {
        locale: locale,
        default: '',
      }),
    },
    monthBeforeYear: i18n.$t('laydate.monthBeforeYear', null, {
      locale: locale,
      default: null,
    }),
    selectDate: i18n.$t('laydate.selectDate', null, {
      locale: locale,
      default: 'Select Date',
    }),
    selectTime: i18n.$t('laydate.selectTime', null, {
      locale: locale,
      default: 'Select Time',
    }),
    startTime: i18n.$t('laydate.startTime', null, {
      locale: locale,
      default: 'Start Time',
    }),
    endTime: i18n.$t('laydate.endTime', null, {
      locale: locale,
      default: 'End Time',
    }),
    tools: {
      confirm: i18n.$t('laydate.tools.confirm', null, {
        locale: locale,
        default: 'Confirm',
      }),
      clear: i18n.$t('laydate.tools.clear', null, {
        locale: locale,
        default: 'Clear',
      }),
      now: i18n.$t('laydate.tools.now', null, {
        locale: locale,
        default: 'Now',
      }),
      reset: i18n.$t('laydate.tools.reset', null, {
        locale: locale,
        default: 'Reset',
      }),
    },
    rangeOrderPrompt: i18n.$t('laydate.rangeOrderPrompt', null, {
      locale: locale,
      default: 'End time cannot be less than start Time\nPlease re-select',
    }),
    invalidDatePrompt: i18n.$t('laydate.invalidDatePrompt', null, {
      locale: locale,
      default: 'Invalid date\n',
    }),
    formatErrorPrompt: function (format) {
      return i18n.$t(
        'laydate.formatErrorPrompt',
        { format: format },
        {
          locale: locale,
          default:
            'Date format is invalid\nMust follow the format:\n{format}\n',
        },
      );
    },
    autoResetPrompt: i18n.$t('laydate.autoResetPrompt', null, {
      locale: locale,
      default: 'It has been reset',
    }),
    preview: i18n.$t('laydate.preview', null, {
      locale: locale,
      default: 'The selected result',
    }),
  };
};

// 仅简体中文生效，不做国际化
Class.prototype.markerOfChineseFestivals = {
  '0-1-1': '元旦',
  '0-2-14': '情人',
  '0-3-8': '妇女',
  '0-3-12': '植树',
  '0-4-1': '愚人',
  '0-5-1': '劳动',
  '0-5-4': '青年',
  '0-6-1': '儿童',
  '0-9-10': '教师',
  '0-10-1': '国庆',
  '0-12-25': '圣诞',
};

// 重载实例
Class.prototype.reload = function (options) {
  var that = this;
  that.config = lay.extend({}, that.config, options, overwriteArray);
  that.init();
};

//初始准备
Class.prototype.init = function () {
  var that = this,
    options = that.config,
    isStatic = options.position === 'static',
    format = {
      year: 'yyyy',
      month: 'yyyy-MM',
      date: 'yyyy-MM-dd',
      time: 'HH:mm:ss',
      datetime: 'yyyy-MM-dd HH:mm:ss',
    };

  options.elem = lay(options.elem);
  options.eventElem = lay(options.eventElem);

  if (!options.elem[0]) return;

  layui.type(options.theme) !== 'array' && (options.theme = [options.theme]);
  // 设置了全面版模式
  if (options.fullPanel) {
    if (options.type !== 'datetime' || options.range) {
      // 目前只支持datetime的全面版
      delete options.fullPanel;
    }
  }

  //日期范围分隔符
  that.rangeStr = options.range
    ? typeof options.range === 'string'
      ? options.range
      : '-'
    : '';

  //日期范围的日历面板是否联动
  that.rangeLinked = !!(
    options.range &&
    options.rangeLinked &&
    (options.type === 'date' || options.type === 'datetime')
  );

  //切换日历联动方式
  that.autoCalendarModel = function () {
    var state = that.rangeLinked;
    that.rangeLinked =
      options.range &&
      (options.type === 'date' || options.type === 'datetime') &&
      (!that.startDate ||
        !that.endDate ||
        (that.startDate &&
          that.endDate &&
          that.startDate.year === that.endDate.year &&
          that.startDate.month === that.endDate.month));
    lay(that.elem)[that.rangeLinked ? 'addClass' : 'removeClass'](
      'layui-laydate-linkage',
    );
    return that.rangeLinked != state; // 返回发生了变化
  };

  //是否自动切换
  that.autoCalendarModel.auto =
    that.rangeLinked && options.rangeLinked === 'auto';

  //若 range 参数为数组，则表示为开始日期和结束日期的 input 对象
  if (layui.type(options.range) === 'array') {
    that.rangeElem = [lay(options.range[0]), lay(options.range[1])];
  }

  //若 type 设置非法，则初始化为 date 类型
  if (!format[options.type]) {
    window.console &&
      console.error &&
      console.error(
        "laydate type error:'" + options.type + "' is not supported",
      );
    options.type = 'date';
  }

  //根据不同 type，初始化默认 format
  if (options.format === format.date) {
    options.format = format[options.type] || format.date;
  }

  //将日期格式转化成数组
  that.format = thisModule.formatArr(options.format);

  // 设置了一周的开始是周几，此处做一个控制
  if (options.weekStart) {
    if (!/^[0-6]$/.test(options.weekStart)) {
      var lang = that.i18nMessages;
      options.weekStart = lang.weeks.indexOf(options.weekStart);
      if (options.weekStart === -1) options.weekStart = 0;
    }
  }

  //生成正则表达式
  that.EXP_IF = '';
  that.EXP_SPLIT = '';
  lay.each(that.format, function (i, item) {
    var EXP = new RegExp(dateType).test(item)
      ? '\\d{' +
        (function () {
          if (
            new RegExp(dateType).test(
              that.format[i === 0 ? i + 1 : i - 1] || '',
            )
          ) {
            if (/^yyyy|y$/.test(item)) return 4;
            return item.length;
          }
          if (/^yyyy$/.test(item)) return '1,4';
          if (/^y$/.test(item)) return '1,308';
          return '1,2';
        })() +
        '}'
      : '\\' + item;
    that.EXP_IF = that.EXP_IF + EXP;
    that.EXP_SPLIT = that.EXP_SPLIT + '(' + EXP + ')';
  });
  //验证日期格式正则
  that.EXP_IF_ONE = new RegExp('^' + that.EXP_IF + '$'); //验证单个日期格式
  that.EXP_IF = new RegExp(
    '^' +
      (options.range
        ? that.EXP_IF + '\\s\\' + that.rangeStr + '\\s' + that.EXP_IF
        : that.EXP_IF) +
      '$',
  );
  that.EXP_SPLIT = new RegExp('^' + that.EXP_SPLIT + '$', '');

  //如果不是 input|textarea 元素，则默认采用 click 事件
  if (!that.isInput(options.elem[0])) {
    if (options.trigger === 'focus') {
      options.trigger = 'click';
    }
  }

  // 设置唯一 KEY
  options.elem.attr('lay-key', that.index);
  options.eventElem.attr('lay-key', that.index);
  options.elem.attr(MOD_ID, options.id); // 渲染过的标记

  //获取限制内日期
  lay.each(['min', 'max'], function (i, item) {
    var ymd = [];
    var hms = [];
    if (typeof options[item] === 'number') {
      //如果为数字
      var day = options[item],
        tDate = new Date(),
        time = that
          .newDate({
            //今天的最大毫秒数
            year: tDate.getFullYear(),
            month: tDate.getMonth(),
            date: tDate.getDate(),
            hours: i ? 23 : 0,
            minutes: i ? 59 : 0,
            seconds: i ? 59 : 0,
          })
          .getTime(),
        STAMP = 86400000, //代表一天的毫秒数
        thisDate = new Date(
          day
            ? day < STAMP
              ? time + day * STAMP
              : day //如果数字小于一天的毫秒数，则数字为天数，否则为毫秒数
            : time,
        );
      ymd = [
        thisDate.getFullYear(),
        thisDate.getMonth() + 1,
        thisDate.getDate(),
      ];
      hms = [thisDate.getHours(), thisDate.getMinutes(), thisDate.getSeconds()];
    } else if (typeof options[item] === 'string') {
      ymd = (options[item].match(/\d+-\d+-\d+/) || [''])[0].split('-');
      hms = (options[item].match(/\d+:\d+:\d+/) || [''])[0].split(':');
    } else if (typeof options[item] === 'object') {
      return options[item];
    }
    options[item] = {
      year: ymd[0] | 0 || new Date().getFullYear(),
      month: ymd[1] ? (ymd[1] | 0) - 1 : new Date().getMonth(),
      date: ymd[2] | 0 || new Date().getDate(),
      hours: hms[0] | 0,
      minutes: hms[1] | 0,
      seconds: hms[2] | 0,
    };
  });

  that.elemID = 'layui-laydate' + options.elem.attr('lay-key');

  if (options.show || isStatic) that.render();
  isStatic || that.events();

  // 重定义 input 元素的 get set
  if (typeof options.formatToDisplay === 'function') {
    if (that.isInput(options.elem[0])) {
      that.formatToDisplay(options.elem[0], options.formatToDisplay);
    } else {
      var rangeElem = that.rangeElem;
      if (rangeElem) {
        that.formatToDisplay(rangeElem[0][0], options.formatToDisplay);
        that.formatToDisplay(rangeElem[1][0], options.formatToDisplay);
      }
    }
  }

  //默认赋值
  if (options.value && options.isInitValue) {
    if (layui.type(options.value) === 'date') {
      that.setValue(that.parse(0, that.systemDate(options.value)));
    } else {
      that.setValue(options.value);
    }
  }
};

//控件主体渲染
Class.prototype.render = function () {
  var that = this,
    options = that.config,
    lang = that.i18nMessages,
    isStatic = options.position === 'static',
    //主面板
    elem = (that.elem = lay.elem('div', {
      id: that.elemID,
      class: [
        'layui-laydate',
        options.range ? ' layui-laydate-range' : '',
        that.rangeLinked ? ' layui-laydate-linkage' : '',
        isStatic ? ' ' + ELEM_STATIC : '',
        options.fullPanel ? ' laydate-theme-fullpanel' : '', // 全面版
        // ,options.theme && options.theme !== 'default' && !/^#/.test(options.theme) ? (' laydate-theme-' + options.theme) : ''
        (function () {
          var themeStr = '';
          lay.each(options.theme, function (index, theme) {
            if (theme !== 'default' && !/^#/.test(theme)) {
              themeStr += ' laydate-theme-' + theme;
            }
          });
          return themeStr;
        })(),
      ].join(''),
    })),
    //主区域
    elemMain = (that.elemMain = []),
    elemHeader = (that.elemHeader = []),
    elemCont = (that.elemCont = []),
    elemTable = (that.table = []),
    //底部区域
    divFooter = (that.footer = lay.elem('div', {
      class: ELEM_FOOTER,
    })),
    //快捷栏
    divShortcut = (that.shortcut = lay.elem('ul', {
      class: ELEM_SHORTCUT,
    }));

  if (options.zIndex) elem.style.zIndex = options.zIndex;

  //单双日历区域
  lay.each(new Array(2), function (i) {
    if (!options.range && i > 0) {
      return true;
    }

    //头部区域
    var divHeader = lay.elem('div', {
        class: 'layui-laydate-header',
      }),
      //左右切换
      headerChild = [
        (function () {
          //上一年
          var elem = lay.elem('i', {
            class: 'layui-icon laydate-icon laydate-prev-y',
          });
          elem.innerHTML = '&#xe65a;';
          return elem;
        })(),
        (function () {
          //上一月
          var elem = lay.elem('i', {
            class: 'layui-icon laydate-icon laydate-prev-m',
          });
          elem.innerHTML = '&#xe603;';
          return elem;
        })(),
        (function () {
          //年月选择
          var elem = lay.elem('div', {
            class: 'laydate-set-ym',
          });
          elem.appendChild(lay.elem('span'));
          elem.appendChild(lay.elem('span'));
          return elem;
        })(),
        (function () {
          //下一月
          var elem = lay.elem('i', {
            class: 'layui-icon laydate-icon laydate-next-m',
          });
          elem.innerHTML = '&#xe602;';
          return elem;
        })(),
        (function () {
          //下一年
          var elem = lay.elem('i', {
            class: 'layui-icon laydate-icon laydate-next-y',
          });
          elem.innerHTML = '&#xe65b;';
          return elem;
        })(),
      ],
      //日历内容区域
      divContent = lay.elem('div', {
        class: 'layui-laydate-content',
      }),
      table = lay.elem('table'),
      thead = lay.elem('thead'),
      theadTr = lay.elem('tr');

    //生成年月选择
    lay.each(headerChild, function (i, item) {
      divHeader.appendChild(item);
    });

    //生成表格
    thead.appendChild(theadTr);
    lay.each(new Array(6), function (i) {
      //表体
      var tr = table.insertRow(0);
      lay.each(new Array(7), function (j) {
        if (i === 0) {
          var th = lay.elem('th');
          th.innerHTML = lang.weeks[(j + options.weekStart) % 7];
          theadTr.appendChild(th);
        }
        tr.insertCell(j);
      });
    });
    table.insertBefore(thead, table.children[0]); //表头
    divContent.appendChild(table);

    elemMain[i] = lay.elem('div', {
      class: ELEM_MAIN + ' laydate-main-list-' + i,
    });

    elemMain[i].appendChild(divHeader);
    elemMain[i].appendChild(divContent);

    elemHeader.push(headerChild);
    elemCont.push(divContent);
    elemTable.push(table);
  });

  //生成底部栏
  lay(divFooter).html(
    (function () {
      var html = [],
        btns = [];
      if (options.type === 'datetime') {
        html.push(
          '<span lay-type="datetime" class="' +
            ELEM_TIME_BTN +
            '">' +
            lang.selectTime +
            '</span>',
        );
      }
      if (
        !(!options.range && options.type === 'datetime') ||
        options.fullPanel
      ) {
        html.push(
          '<span class="' +
            ELEM_PREVIEW +
            '" title="' +
            lang.preview +
            '"></span>',
        );
      }

      lay.each(options.btns, function (i, item) {
        var title = lang.tools[item] || 'btn';
        if (options.range && item === 'now') return;
        if (isStatic && item === 'clear') {
          title = lang.tools.reset;
        }
        btns.push(
          '<span lay-type="' +
            item +
            '" class="laydate-btns-' +
            item +
            '">' +
            title +
            '</span>',
        );
      });
      html.push('<div class="laydate-footer-btns">' + btns.join('') + '</div>');
      return html.join('');
    })(),
  );

  // 生成快捷键栏
  if (options.shortcuts) {
    elem.appendChild(divShortcut);
    lay(divShortcut)
      .html(
        (function () {
          var shortcutBtns = [];
          lay.each(options.shortcuts, function (i, item) {
            shortcutBtns.push(
              '<li data-index="' + i + '">' + item.text + '</li>',
            );
          });
          return shortcutBtns.join('');
        })(),
      )
      .find('li')
      .on('click', function () {
        var btnSetting = options.shortcuts[this.dataset['index']] || {};
        var value =
          (typeof btnSetting.value === 'function'
            ? btnSetting.value()
            : btnSetting.value) || [];
        if (!layui.isArray(value)) {
          value = [value];
        }
        var type = options.type;
        lay.each(value, function (i, item) {
          var dateTime = [options.dateTime, that.endDate][i];
          if (type === 'time' && layui.type(item) !== 'date') {
            if (that.EXP_IF.test(item)) {
              item = (item.match(that.EXP_SPLIT) || []).slice(1);
              lay.extend(dateTime, {
                hours: item[0] | 0,
                minutes: item[2] | 0,
                seconds: item[4] | 0,
              });
            }
          } else {
            lay.extend(
              dateTime,
              that.systemDate(
                layui.type(item) === 'date' ? item : new Date(item),
              ),
            );
          }

          if (type === 'time' || type === 'datetime') {
            that[['startTime', 'endTime'][i]] = {
              hours: dateTime.hours,
              minutes: dateTime.minutes,
              seconds: dateTime.seconds,
            };
          }
          if (i === 0) {
            // 第一个值作为startDate
            that.startDate = lay.extend({}, dateTime);
          } else {
            that.endState = true;
          }
          if (type === 'year' || type === 'month' || type === 'time') {
            that.listYM[i] = [dateTime.year, dateTime.month + 1];
          } else if (i) {
            that.autoCalendarModel.auto && that.autoCalendarModel();
          }
        });
        that.checkDate('limit').calendar(null, null, 'init');

        var timeBtn = lay(that.footer)
          .find('.' + ELEM_TIME_BTN)
          .removeClass(DISABLED);
        timeBtn && timeBtn.attr('lay-type') === 'date' && timeBtn[0].click();
        that.done(null, 'change');

        lay(this).addClass(THIS);

        // 自动确认
        if (options.position !== 'static') {
          that.setValue(that.parse()).done().remove();
        }
        /*
      if (options.position !== 'static' && !options.range && options.autoConfirm) {
        if (type === 'date') {
          that.choose(lay(elem).find('td.layui-this'))
        } else if (type === 'year' || type === 'month') {
          if(lay(elemMain[0]).find('.' + ELEM_MAIN + ' li.' + THIS + ':not(.laydate-disabled)')[0]) {
            that.setValue(that.parse()).done().remove();
          }
        }
      }
      */
      });
  }

  //插入到主区域
  lay.each(elemMain, function (i, main) {
    elem.appendChild(main);
  });
  options.showBottom && elem.appendChild(divFooter);

  // 生成自定义主题
  var style = lay.elem('style');
  var styleText = [];
  var colorTheme;
  var isPrimaryColor = true;
  lay.each(options.theme, function (index, theme) {
    // 主色
    if (isPrimaryColor && /^#/.test(theme)) {
      colorTheme = true;
      isPrimaryColor = false;
      styleText.push(
        [
          '#{{id}} .layui-laydate-header{background-color:{{theme}};}',
          '#{{id}} li.layui-this,#{{id}} td.layui-this>div{background-color:{{theme}} !important;}',
          options.theme.indexOf('circle') !== -1
            ? ''
            : '#{{id}} .layui-this{background-color:{{theme}} !important;}',
          '#{{id}} .laydate-day-now{color:{{theme}} !important;}',
          '#{{id}} .laydate-day-now:after{border-color:{{theme}} !important;}',
        ]
          .join('')
          .replace(/{{id}}/g, that.elemID)
          .replace(/{{theme}}/g, theme),
      );
      return;
    }
    // 第二个自定义颜色作为辅色
    if (!isPrimaryColor && /^#/.test(theme)) {
      styleText.push(
        [
          '#{{id}} .laydate-selected>div{background-color:{{theme}} !important;}',
          '#{{id}} .laydate-selected:hover>div{background-color:{{theme}} !important;}',
        ]
          .join('')
          .replace(/{{id}}/g, that.elemID)
          .replace(/{{theme}}/g, theme),
      );
    }
  });
  //快捷栏样式
  if (options.shortcuts && options.range) {
    styleText.push(
      '#{{id}}.layui-laydate-range{width: 628px;}'.replace(
        /{{id}}/g,
        that.elemID,
      ),
    );
  }
  if (styleText.length) {
    styleText = styleText.join('');
    if ('styleSheet' in style) {
      style.setAttribute('type', 'text/css');
      style.styleSheet.cssText = styleText;
    } else {
      style.innerHTML = styleText;
    }

    colorTheme && lay(elem).addClass('laydate-theme-molv');
    elem.appendChild(style);
  }

  //移除上一个控件
  that.remove(Class.thisElemDate);

  //记录当前执行的实例索引
  laydate.thisId = options.id;

  //如果是静态定位，则插入到指定的容器中，否则，插入到body
  isStatic
    ? options.elem.append(elem)
    : (document.body.appendChild(elem), that.position()); //定位

  var shade = options.shade
    ? '<div class="' +
      ELEM_SHADE +
      '" style="' +
      ('z-index:' +
        (parseInt(layui.getStyle(elem, 'z-index')) - 1) +
        '; background-color: ' +
        (options.shade[1] || '#000') +
        '; opacity: ' +
        (options.shade[0] || options.shade)) +
      '"></div>'
    : '';
  elem.insertAdjacentHTML('beforebegin', shade);

  that.checkDate().calendar(null, 0, 'init'); //初始校验
  that.changeEvent(); //日期切换

  Class.thisElemDate = that.elemID;

  that.renderAdditional();
  typeof options.ready === 'function' &&
    options.ready(
      lay.extend({}, options.dateTime, {
        month: options.dateTime.month + 1,
      }),
    );

  that.preview();
};

//控件移除
Class.prototype.remove = function (prev) {
  var that = this,
    options = that.config,
    elem = lay('#' + (prev || that.elemID));
  if (!elem[0]) return that;

  if (!elem.hasClass(ELEM_STATIC)) {
    that.checkDate(function () {
      elem.remove();
      //delete options.dateTime;
      delete that.startDate;
      delete that.endDate;
      delete that.endState;
      delete that.startTime;
      delete that.endTime;
      delete laydate.thisId;
      typeof options.close === 'function' && options.close(that);
    });
  }
  lay('.' + ELEM_SHADE).remove();
  return that;
};

//定位算法
Class.prototype.position = function () {
  var that = this,
    options = that.config;
  lay.position(options.elem[0], that.elem, {
    position: options.position,
  });
  return that;
};

// 提示
Class.prototype.hint = function (opts) {
  var that = this;
  // var options = that.config;
  var div = lay.elem('div', {
    class: ELEM_HINT,
  });

  if (!that.elem) return;

  // 兼容旧版参数
  if (typeof opts === 'object') {
    opts = opts || {};
  } else {
    opts = {
      content: opts,
    };
  }

  div.innerHTML = opts.content || '';
  lay(that.elem)
    .find('.' + ELEM_HINT)
    .remove();
  that.elem.appendChild(div);

  clearTimeout(that.hinTimer);
  that.hinTimer = setTimeout(
    function () {
      lay(that.elem)
        .find('.' + ELEM_HINT)
        .remove();
    },
    'ms' in opts ? opts.ms : 3000,
  );
};

//获取递增/减后的年月
Class.prototype.getAsYM = function (Y, M, type) {
  type ? M-- : M++;
  if (M < 0) {
    M = 11;
    Y--;
  }
  if (M > 11) {
    M = 0;
    Y++;
  }
  return [Y, M];
};

//系统日期
Class.prototype.systemDate = function (newDate) {
  var thisDate = newDate || new Date();
  return {
    year: thisDate.getFullYear(), //年
    month: thisDate.getMonth(), //月
    date: thisDate.getDate(), //日
    hours: newDate ? newDate.getHours() : 0, //时
    minutes: newDate ? newDate.getMinutes() : 0, //分
    seconds: newDate ? newDate.getSeconds() : 0, //秒
  };
};

//日期校验
Class.prototype.checkDate = function (fn) {
  var that = this,
    // thisDate = new Date(),
    options = that.config,
    lang = that.i18nMessages,
    dateTime = (options.dateTime = options.dateTime || that.systemDate()),
    thisMaxDate,
    error,
    elem = options.elem[0],
    // valType = that.isInput(elem) ? 'val' : 'html',
    value = (function () {
      //如果传入了开始和结束日期的 input 对象，则将其拼接为日期范围字符
      if (that.rangeElem) {
        var vals = [that.rangeElem[0].val(), that.rangeElem[1].val()];

        if (vals[0] && vals[1]) {
          return vals.join(' ' + that.rangeStr + ' ');
        }
      }
      return that.isInput(elem)
        ? elem.value
        : options.position === 'static'
          ? ''
          : lay(elem).attr('lay-date');
    })(),
    //校验日期有效数字
    checkValid = function (dateTime) {
      if (!dateTime) {
        return;
      }
      if (dateTime.year > LIMIT_YEAR[1])
        ((dateTime.year = LIMIT_YEAR[1]), (error = true)); //不能超过20万年
      if (dateTime.month > 11) ((dateTime.month = 11), (error = true));
      if (dateTime.seconds > 59)
        ((dateTime.seconds = 0), dateTime.minutes++, (error = true));
      if (dateTime.minutes > 59)
        ((dateTime.minutes = 0), dateTime.hours++, (error = true));
      if (dateTime.hours > 23) ((dateTime.hours = 0), (error = true));

      //计算当前月的最后一天
      thisMaxDate = laydate.getEndDate(dateTime.month + 1, dateTime.year);
      if (dateTime.date > thisMaxDate)
        ((dateTime.date = thisMaxDate), (error = true));
    },
    //获得初始化日期值
    initDate = function (dateTime, value, index) {
      var startEnd = ['startTime', 'endTime'];
      value = (value.match(that.EXP_SPLIT) || []).slice(1);
      index = index || 0;

      if (options.range) {
        that[startEnd[index]] = that[startEnd[index]] || {};
      }
      lay.each(that.format, function (i, item) {
        var thisv = parseFloat(value[i]);
        if (value[i].length < item.length) error = true;
        if (/yyyy|y/.test(item)) {
          //年
          if (thisv < LIMIT_YEAR[0]) ((thisv = LIMIT_YEAR[0]), (error = true)); //年不能低于100年
          dateTime.year = thisv;
        } else if (/MM|M/.test(item)) {
          //月
          if (thisv < 1) ((thisv = 1), (error = true));
          dateTime.month = thisv - 1;
        } else if (/dd|d/.test(item)) {
          //日
          if (thisv < 1) ((thisv = 1), (error = true));
          dateTime.date = thisv;
        } else if (/HH|H/.test(item)) {
          //时
          if (thisv < 0) ((thisv = 0), (error = true));
          if (thisv > 23) ((thisv = 23), (error = true));
          dateTime.hours = thisv;
          options.range && (that[startEnd[index]].hours = thisv);
        } else if (/mm|m/.test(item)) {
          //分
          if (thisv < 0) ((thisv = 0), (error = true));
          if (thisv > 59) ((thisv = 59), (error = true));
          dateTime.minutes = thisv;
          options.range && (that[startEnd[index]].minutes = thisv);
        } else if (/ss|s/.test(item)) {
          //秒
          if (thisv < 0) ((thisv = 0), (error = true));
          if (thisv > 59) ((thisv = 59), (error = true));
          dateTime.seconds = thisv;
          options.range && (that[startEnd[index]].seconds = thisv);
        }
      });
      checkValid(dateTime);
    };

  if (fn === 'limit') {
    if (options.range) {
      checkValid(that.rangeLinked ? that.startDate : dateTime); // 校验开始时间
      that.endDate && checkValid(that.endDate); // 校验结束时间
    } else {
      checkValid(dateTime);
    }
    return that;
  }

  value = value || options.value;
  if (typeof value === 'string') {
    value = value.replace(/\s+/g, ' ').replace(/^\s|\s$/g, '');
  }

  //如果开启范围，则计算结束日期
  var getEndDate = function () {
    if (options.range) {
      that.endDate =
        that.endDate ||
        lay.extend(
          {},
          options.dateTime,
          (function () {
            var obj = {},
              dateTime = options.dateTime,
              EYM = that.getAsYM(dateTime.year, dateTime.month);

            //初始右侧面板的年月
            if (options.type === 'year') {
              obj.year = dateTime.year + 1;
            } else if (options.type !== 'time') {
              obj.year = EYM[0];
              obj.month = EYM[1];
            }

            //初始右侧面板的时间
            if (options.type === 'datetime' || options.type === 'time') {
              obj.hours = 23;
              obj.minutes = obj.seconds = 59;
            }

            return obj;
          })(),
        );
    }
  };
  getEndDate();

  if (typeof value === 'string' && value) {
    if (that.EXP_IF.test(value)) {
      //校验日期格式
      if (options.range) {
        value = value.split(' ' + that.rangeStr + ' ');
        lay.each([options.dateTime, that.endDate], function (i, item) {
          initDate(item, value[i], i);
        });
      } else {
        initDate(dateTime, value);
      }
    } else {
      //格式不合法
      that.hint(
        lang.formatErrorPrompt(
          options.range
            ? options.format + ' ' + that.rangeStr + ' ' + options.format
            : options.format,
        ) + lang.autoResetPrompt,
      );
      error = true;
    }
  } else if (value && layui.type(value) === 'date') {
    //若值为日期对象
    options.dateTime = that.systemDate(value);
  } else {
    //重置开始日期
    options.dateTime = that.systemDate();
    delete that.startTime;

    //重置结束日期
    delete that.endDate; //删除原有的结束日期
    getEndDate(); //并重新获得新的结束日期
    delete that.endTime;
  }

  //从日期范围表单中获取初始值
  (function () {
    if (that.rangeElem) {
      var vals = [that.rangeElem[0].val(), that.rangeElem[1].val()],
        arrDate = [options.dateTime, that.endDate];
      lay.each(vals, function (_i, _v) {
        if (that.EXP_IF_ONE.test(_v)) {
          //校验日期格式
          initDate(arrDate[_i], _v, _i);
        }
      });
    }
  })();

  // 校验日期有效数字
  checkValid(dateTime);
  if (options.range) checkValid(that.endDate);

  // 如果初始值格式错误，则纠正初始值
  if (error && value) {
    that.setValue(
      options.range ? (that.endDate ? that.parse() : '') : that.parse(),
    );
  }

  //如果当前日期不在设定的最大小日期区间，则自动纠正在可选区域
  //校验主面板是否在可选日期区间
  var minMaxError;
  if (that.getDateTime(dateTime) > that.getDateTime(options.max)) {
    //若超出最大日期
    dateTime = options.dateTime = lay.extend({}, options.max);
    minMaxError = true;
  } else if (that.getDateTime(dateTime) < that.getDateTime(options.min)) {
    //若少于最小日期
    dateTime = options.dateTime = lay.extend({}, options.min);
    minMaxError = true;
  }

  //校验右侧面板是否在可选日期区间
  if (options.range) {
    if (
      that.getDateTime(that.endDate) < that.getDateTime(options.min) ||
      that.getDateTime(that.endDate) > that.getDateTime(options.max)
    ) {
      that.endDate = lay.extend({}, options.max);
      minMaxError = true;
    }
    // 有时间范围的情况下初始化startTime和endTime
    that.startTime = {
      hours: options.dateTime.hours,
      minutes: options.dateTime.minutes,
      seconds: options.dateTime.seconds,
    };
    that.endTime = {
      hours: that.endDate.hours,
      minutes: that.endDate.minutes,
      seconds: that.endDate.seconds,
    };
    // 如果是年月范围，将对应的日期统一成当月的1日进行比较，避免出现同一个月但是开始日期大于结束日期的情况
    if (options.type === 'month') {
      options.dateTime.date = 1;
      that.endDate.date = 1;
    }
  }

  // 初始值不在最大最小范围内
  if (minMaxError && value) {
    that.setValue(that.parse());
    that.hint('value ' + lang.invalidDatePrompt + lang.autoResetPrompt);
  }

  // 初始赋值 startDate,endState
  that.startDate =
    that.startDate || (value && lay.extend({}, options.dateTime)); // 有默认值才初始化startDate
  that.autoCalendarModel.auto && that.autoCalendarModel();
  that.endState =
    !options.range || !that.rangeLinked || !!(that.startDate && that.endDate); // 初始化选中范围状态

  fn && fn();
  return that;
};

/**
 * 渲染备注
 * @param {JQuery} tdElem td 元素
 * @param {[number, number, number]} YMD 年月日
 * @param {object | string} markers 备注信息
 */
Class.prototype.markRender = function (tdElem, YMD, markers) {
  var markText;

  if (typeof markers === 'object') {
    lay.each(markers || {}, function (key, title) {
      var keys = key.split('-');
      if (
        (keys[0] == YMD[0] || keys[0] == 0) && //每年的每月
        (keys[1] == YMD[1] || keys[1] == 0) && //每月的每日
        keys[2] == YMD[2]
      ) {
        //特定日
        markText = title || YMD[2];
      }
    });
  } else if (typeof markers === 'string') {
    markText = markers || YMD[2];
  }

  markText &&
    tdElem
      .find('div')
      .html('<span class="laydate-day-mark">' + markText + '</span>');
};

/**
 * 公历重要日期与自定义备注
 * @param {JQuery} td
 * @param {[number, number, number]} YMD
 * @returns Class
 */
Class.prototype.mark = function (td, YMD) {
  var that = this;
  var options = that.config;

  var render = function (markers) {
    that.markRender(td, YMD, markers);
  };

  // chineseFestivals 仅简体中文生效
  if (options.calendar) {
    if (options.lang === zhCN) {
      render(that.markerOfChineseFestivals);
    }
  }

  if (typeof options.mark === 'function') {
    options.mark({ year: YMD[0], month: YMD[1], date: YMD[2] }, render);
  } else if (typeof options.mark === 'object') {
    render(options.mark);
  }

  return that;
};

/**
 * 渲染法定节假日或补假上班标记
 * @param {JQuery} tdElem td 元素
 * @param {[number, number, number]} YMD 年月日
 * @param {[Array<string>, Array<string>] | string} markers 标记信息
 */
Class.prototype.holidaysRender = function (tdElem, YMD, markers) {
  var type = ['holidays', 'workdays'];

  var isEquals = function (ymdStr1, ymdStr2) {
    var ymd1 = ymdStr1.split('-');
    var ymd2 = ymdStr2.split('-');

    lay.each(ymd1, function (i, v) {
      ymd1[i] = parseInt(v, 10);
    });
    lay.each(ymd2, function (i, v) {
      ymd2[i] = parseInt(v, 10);
    });

    return ymd1.join('-') === ymd2.join('-');
  };

  var insertHtml = function (el, type, text) {
    el.find('div').html(
      [
        '<span',
        ' class="laydate-day-holidays"',
        ' type="' + type + '"',
        '>',
        text,
        '</span>',
      ].join(''),
    );
  };

  if (layui.type(markers) === 'array') {
    lay.each(markers, function (idx, item) {
      lay.each(item, function (i, dayStr) {
        if (isEquals(dayStr, tdElem.attr('lay-ymd'))) {
          insertHtml(tdElem, type[idx], YMD[2]);
        }
      });
    });
  } else if (typeof markers === 'string') {
    if (type.indexOf(markers) !== -1) {
      insertHtml(tdElem, markers, YMD[2]);
    }
  }
};

/**
 * 标注法定节假日或补假上班
 * @param {JQuery} td
 * @param {[number, number, number]} YMD
 * @returns Class
 */
Class.prototype.holidays = function (td, YMD) {
  var that = this;
  var options = that.config;

  var render = function (markers) {
    that.holidaysRender(td, YMD, markers);
  };

  if (typeof options.holidays === 'function') {
    options.holidays({ year: YMD[0], month: YMD[1], date: YMD[2] }, render);
  } else if (layui.type(options.holidays) === 'array') {
    render(options.holidays);
  }

  return that;
};

/**
 * 自定义单元格
 * @param {HTMLElement|Array<HTMLElement>} el - 单元格元素
 * @param {{year:number, month:number, date:number}} dateObj - 当前单元格对应的日期信息
 * @param {'year' | 'month' | 'date'} panelMode - 面板模式
 * @returns
 */
Class.prototype.cellRender = function (el, dateObj, panelMode) {
  var that = this;
  var options = that.config;

  if (typeof options.cellRender === 'function') {
    var render = function (content) {
      if (typeof content === 'string') {
        lay(el).html(content);
      } else if (typeof content === 'object') {
        lay(el).html('').append(lay(content)[0]);
      }
    };
    options.cellRender(dateObj, render, { originElem: el, type: panelMode });
  }

  return that;
};

/**
 * 给定年份的开始日期
 * @param {Date} date
 */
Class.prototype.startOfYear = function (date) {
  var newDate = new Date(date);
  newDate.setFullYear(newDate.getFullYear(), 0, 1);
  newDate.setHours(0, 0, 0, 0);
  return newDate;
};

/**
 * 给定年份的结束日期
 * @param {Date} date
 */
Class.prototype.endOfYear = function (date) {
  var newDate = new Date(date);
  var year = newDate.getFullYear();
  newDate.setFullYear(year + 1, 0, 0);
  newDate.setHours(23, 59, 59, 999);
  return newDate;
};

/**
 * 给定月份的开始日期
 * @param {Date} date
 */
Class.prototype.startOfMonth = function (date) {
  var newDate = new Date(date);
  newDate.setDate(1);
  newDate.setHours(0, 0, 0, 0);
  return newDate;
};

/**
 * 给定月份的结束日期
 * @param {Date} date
 */
Class.prototype.endOfMonth = function (date) {
  var newDate = new Date(date);
  var month = newDate.getMonth();
  newDate.setFullYear(newDate.getFullYear(), month + 1, 0);
  newDate.setHours(23, 59, 59, 999);
  return newDate;
};

/**
 * 将指定的天数添加到给定日期
 * @param {Date} date 要更改的日期
 * @param {number} amount 天数
 */
Class.prototype.addDays = function (date, amount) {
  var newDate = new Date(date);
  if (!amount) return newDate;
  newDate.setDate(newDate.getDate() + amount);
  return newDate;
};

/**
 * 不可选取的年或月。年或月中的所有日期都禁用时，才判定为不可选取。
 * @param {Date} date 要检测的年或月
 * @param {'year' | 'month'} type 面板类型
 * @param {'start' | 'end'} position 面板位置
 */
Class.prototype.isDisabledYearOrMonth = function (date, type, position) {
  var that = this;
  var options = that.config;
  var millisecondsInDay = 24 * 60 * 60 * 1000;

  var startDay =
    type === 'year' ? that.startOfYear(date) : that.startOfMonth(date);
  var endDay = type === 'year' ? that.endOfYear(date) : that.endOfMonth(date);
  var numOfDays =
    Math.floor((endDay.getTime() - startDay.getTime()) / millisecondsInDay) + 1;
  var disabledCount = 0;

  for (var i = 0; i < numOfDays; i++) {
    var day = that.addDays(startDay, i);
    if (options.disabledDate.call(options, day, position)) {
      disabledCount++;
    }
  }

  return disabledCount === numOfDays;
};

/**
 * @typedef limitOptions
 * @prop {JQuery} [elem] - 检测的元素, 例如面板中年月日时分秒元素，“现在”，“确认” 按钮等
 * @prop {number} [index] - 元素集合中，当前检测元素的索引，years:0,month:0,date:0-41,hms:0
 * @prop {['hours', 'minutes', 'seconds'] | ['hours', 'minutes'] | ['hours']} [time] - 是否比较时分秒
 * @prop {'year'|'month'|string} [type] - 面板类型?
 * @prop {0 | 1} [rangeType] - 面板索引, 0 表示 start, 1 表示 end
 * @prop {Partial<{year:number,month: number,date:number,hours:number,minutes:number,seconds:number}>} [date] - 检测的日期时间对象
 * @prop {'date' | 'time' | 'datetime'} disabledType - 禁用类型，按钮应使用 datetime
 */
/**
 * 不可选取的日期
 * @param {number} date 当前检测的日期的时间戳
 * @param {limitOptions} opts
 * @returns {boolean}
 */
Class.prototype.isDisabledDate = function (date, opts) {
  opts = opts || {};

  var that = this;
  var options = that.config;
  var position = options.range
    ? opts.rangeType === 0
      ? 'start'
      : 'end'
    : 'start';

  if (!options.disabledDate) return false;
  if (options.type === 'time') return false;
  if (!(opts.disabledType === 'date' || opts.disabledType === 'datetime'))
    return false;

  // 不需要时分秒
  var normalizedDate = new Date(date);
  normalizedDate.setHours(0, 0, 0, 0);

  return opts.type === 'year' || opts.type === 'month'
    ? that.isDisabledYearOrMonth(normalizedDate, opts.type, position)
    : options.disabledDate.call(options, normalizedDate, position);
};

/**
 * 不可选取的时间
 * @param {number} date 当前检测的日期的时间戳
 * @param {limitOptions} opts
 * @returns {boolean}
 */
Class.prototype.isDisabledTime = function (date, opts) {
  opts = opts || {};

  var that = this;
  var options = that.config;
  var position = options.range
    ? opts.rangeType === 0
      ? 'start'
      : 'end'
    : 'start';

  if (!options.disabledTime) return false;
  if (!(options.type === 'time' || options.type === 'datetime')) return false;
  if (!(opts.disabledType === 'time' || opts.disabledType === 'datetime'))
    return false;

  var isDisabledItem = function (compareVal, rangeFn, rangeFnParam) {
    return function () {
      return (
        (
          (typeof rangeFn === 'function' &&
            rangeFn.apply(options, rangeFnParam)) ||
          []
        ).indexOf(compareVal) !== -1
      );
    };
  };

  var dateObj = that.systemDate(new Date(date));
  var disabledTime =
    options.disabledTime.call(options, that.newDate(dateObj), position) || {};

  // 面板中的时分秒 HTML 元素需要分别检测是否禁用
  // 按钮检测任意一项是否禁用即可
  return opts.disabledType === 'datetime'
    ? isDisabledItem(dateObj.hours, disabledTime.hours)() ||
        isDisabledItem(dateObj.minutes, disabledTime.minutes, [
          dateObj.hours,
        ])() ||
        isDisabledItem(dateObj.seconds, disabledTime.seconds, [
          dateObj.hours,
          dateObj.minutes,
        ])()
    : [
        isDisabledItem(dateObj.hours, disabledTime.hours),
        isDisabledItem(dateObj.minutes, disabledTime.minutes, [dateObj.hours]),
        isDisabledItem(dateObj.seconds, disabledTime.seconds, [
          dateObj.hours,
          dateObj.minutes,
        ]),
      ][opts.time.length - 1]();
};

/**
 * 不可选取的日期时间
 * @param {number} timestamp 当前检测的日期的时间戳
 * @param {limitOptions} opts
 * @returns
 */
Class.prototype.isDisabledDateTime = function (timestamp, opts) {
  opts = opts || {};

  var that = this;
  // var options = that.config;

  return (
    that.isDisabledDate(timestamp, opts) || that.isDisabledTime(timestamp, opts)
  );
};

/**
 * 无效日期范围的标记
 * @param {limitOptions} opts
 *
 */
Class.prototype.limit = function (opts) {
  opts = opts || {};

  var that = this;
  var options = that.config;
  var timestamp = {};
  var dateTime =
    opts.index > (opts.time ? 0 : 41) ? that.endDate : options.dateTime;
  var isOut;

  lay.each(
    {
      now: lay.extend({}, dateTime, opts.date || {}),
      min: options.min,
      max: options.max,
    },
    function (key, item) {
      timestamp[key] = that
        .newDate(
          lay.extend(
            {
              year: item.year,
              month: opts.type === 'year' ? 0 : item.month, // 年份的时候只比较年
              date:
                opts.type === 'year' || opts.type === 'month' ? 1 : item.date, // 年月只比较年月不与最大最小比日期
            },
            (function () {
              var hms = {};
              lay.each(opts.time, function (i, keys) {
                hms[keys] = item[keys];
              });
              return hms;
            })(),
          ),
        )
        .getTime(); //time：是否比较时分秒
    },
  );

  isOut =
    timestamp.now < timestamp.min ||
    timestamp.now > timestamp.max ||
    that.isDisabledDateTime(timestamp.now, opts);
  opts.elem && opts.elem[isOut ? 'addClass' : 'removeClass'](DISABLED);

  return isOut;
};

//当前日期对象
Class.prototype.thisDateTime = function (index) {
  var that = this,
    options = that.config;
  return index ? that.endDate : options.dateTime;
};

//日历表
Class.prototype.calendar = function (value, index, type) {
  index = index ? 1 : 0;
  var that = this,
    options = that.config,
    dateTime = value || that.thisDateTime(index),
    thisDate = new Date(),
    startWeek,
    prevMaxDate,
    thisMaxDate,
    lang = that.i18nMessages,
    isAlone = options.type !== 'date' && options.type !== 'datetime',
    tds = lay(that.table[index]).find('td'),
    elemYM = lay(that.elemHeader[index][2]).find('span');

  if (dateTime.year < LIMIT_YEAR[0])
    ((dateTime.year = LIMIT_YEAR[0]), that.hint(lang.invalidDatePrompt));
  if (dateTime.year > LIMIT_YEAR[1])
    ((dateTime.year = LIMIT_YEAR[1]), that.hint(lang.invalidDatePrompt));

  //记录初始值
  if (!that.firstDate) {
    that.firstDate = lay.extend({}, dateTime);
  }

  //计算当前月第一天的星期
  thisDate.setFullYear(dateTime.year, dateTime.month, 1);
  startWeek = (thisDate.getDay() + (7 - options.weekStart)) % 7;

  prevMaxDate = laydate.getEndDate(dateTime.month || 12, dateTime.year); //计算上个月的最后一天
  thisMaxDate = laydate.getEndDate(dateTime.month + 1, dateTime.year); //计算当前月的最后一天

  //赋值日
  lay.each(tds, function (index_, item) {
    var YMD = [dateTime.year, dateTime.month],
      st;
    item = lay(item);
    item.removeAttr('class');
    if (index_ < startWeek) {
      st = prevMaxDate - startWeek + index_;
      item.addClass('laydate-day-prev');
      YMD = that.getAsYM(dateTime.year, dateTime.month, 'sub');
    } else if (index_ >= startWeek && index_ < thisMaxDate + startWeek) {
      st = index_ - startWeek;
      if (!that.rangeLinked) {
        st + 1 === dateTime.date && item.addClass(THIS);
      }
    } else {
      st = index_ - thisMaxDate - startWeek;
      item.addClass('laydate-day-next');
      YMD = that.getAsYM(dateTime.year, dateTime.month);
    }
    YMD[1]++;
    YMD[2] = st + 1;
    item.attr('lay-ymd', YMD.join('-')).html('<div>' + YMD[2] + '</div>');
    that
      .mark(item, YMD)
      .holidays(item, YMD)
      .limit({
        elem: item,
        date: {
          year: YMD[0],
          month: YMD[1] - 1,
          date: YMD[2],
        },
        index: index_,
        rangeType: index,
        disabledType: 'date', // 日面板，检测当前日期是否禁用
      });
    that.cellRender(
      item,
      { year: YMD[0], month: YMD[1], date: YMD[2] },
      'date',
    );
  });

  //同步头部年月
  lay(elemYM[0]).attr('lay-ym', dateTime.year + '-' + (dateTime.month + 1));
  lay(elemYM[1]).attr('lay-ym', dateTime.year + '-' + (dateTime.month + 1));
  if (!that.panelYM) that.panelYM = {};
  that.panelYM[index] = { year: dateTime.year, month: dateTime.month };

  var normalizedYearStr = addSpaceBetweenChars(
    dateTime.year + lang.literal.year,
  );
  var normalizedMonthStr = addSpaceBetweenChars(lang.months[dateTime.month]);
  if (!lang.monthBeforeYear) {
    lay(elemYM[0]).attr('lay-type', 'year').html(normalizedYearStr);
    lay(elemYM[1]).attr('lay-type', 'month').html(normalizedMonthStr);
  } else {
    lay(elemYM[0]).attr('lay-type', 'month').html(normalizedMonthStr);
    lay(elemYM[1]).attr('lay-type', 'year').html(normalizedYearStr);
  }

  //初始默认选择器
  if (isAlone) {
    //年、月等独立选择器
    if (options.range) {
      if (value || type !== 'init') {
        // 判断是否需要显示年月时间列表
        that.listYM = [
          [
            (that.startDate || options.dateTime).year,
            (that.startDate || options.dateTime).month + 1,
          ],
          [that.endDate.year, that.endDate.month + 1],
        ];
        that.list(options.type, 0).list(options.type, 1);

        //同步按钮可点状态
        options.type === 'time'
          ? that.setBtnStatus(
              true,
              lay.extend({}, that.systemDate(), that.startTime),
              lay.extend({}, that.systemDate(), that.endTime),
            )
          : that.setBtnStatus(true);
      }
    } else {
      that.listYM = [[dateTime.year, dateTime.month + 1]];
      that.list(options.type, 0);
    }
  }

  //初始赋值双日历
  if (options.range && type === 'init') {
    //执行渲染第二个日历
    if (that.rangeLinked) {
      var EYM = that.getAsYM(
        dateTime.year,
        dateTime.month,
        index ? 'sub' : null,
      );
      that.calendar(
        lay.extend({}, dateTime, {
          year: EYM[0],
          month: EYM[1],
        }),
        1 - index,
      ); // 渲染另外一个
    } else {
      that.calendar(null, 1 - index);
    }
  }

  // 通过检测当前有效日期，来设定底部按钮状态
  if (!options.range) {
    var timeParams = ['hours', 'minutes', 'seconds'];

    // 现在按钮
    that.limit({
      elem: lay(that.footer).find(ELEM_NOW),
      date: that.systemDate(
        /^(datetime|time)$/.test(options.type) ? new Date() : null,
      ),
      index: 0,
      time: timeParams,
      disabledType: 'datetime', // 按钮，检测日期和时间
    });
    // 确认按钮
    that.limit({
      elem: lay(that.footer).find(ELEM_CONFIRM),
      index: 0,
      time: timeParams,
      disabledType: 'datetime', // 按钮，检测日期和时间
    });
  }

  //同步按钮可点状态
  that.setBtnStatus();

  // 重置快捷栏选中状态
  lay(that.shortcut)
    .find('li.' + THIS)
    .removeClass(THIS);

  //标记选择范围
  if (options.range && !isAlone && type !== 'init') that.stampRange();

  return that;
};

//生成年月时分秒列表
Class.prototype.list = function (type, index) {
  var that = this,
    options = that.config,
    dateTime = that.rangeLinked
      ? options.dateTime
      : [options.dateTime, that.endDate][index],
    lang = that.i18nMessages,
    isAlone =
      options.range && options.type !== 'date' && options.type !== 'datetime', //独立范围选择器
    ul = lay.elem('ul', {
      class:
        ELEM_LIST +
        ' ' +
        {
          year: 'laydate-year-list',
          month: 'laydate-month-list',
          time: 'laydate-time-list',
        }[type],
    }),
    elemHeader = that.elemHeader[index],
    elemYM = lay(elemHeader[2]).find('span'),
    elemCont = that.elemCont[index || 0],
    haveList = lay(elemCont).find('.' + ELEM_LIST)[0],
    isMonthBeforeYear = lang.monthBeforeYear,
    text = lang.literal.year,
    listYM = that.listYM[index] || {},
    hms = ['hours', 'minutes', 'seconds'],
    startEnd = ['startTime', 'endTime'][index];

  if (listYM[0] < 1) listYM[0] = 1;

  //生成年列表
  if (type === 'year') {
    var yearNum,
      startY = (yearNum = listYM[0] - 7);
    if (startY < 1) startY = yearNum = 1;
    lay.each(new Array(15), function () {
      var li = lay.elem('li', {
          'lay-ym': yearNum,
        }),
        ymd = {
          year: yearNum,
          month: 0,
          date: 1,
        };

      yearNum == listYM[0] && lay(li).addClass(THIS);
      li.innerHTML = yearNum + text;
      ul.appendChild(li);

      /*
      if(yearNum < that.firstDate.year){
        ymd.month = options.min.month;
        ymd.date = options.min.date;
      } else if(yearNum >= that.firstDate.year){
        ymd.month = options.max.month;
        ymd.date = options.max.date;
      }
      */

      that.limit({
        elem: lay(li),
        date: ymd,
        index: index,
        type: type,
        rangeType: index,
        disabledType: 'date', // 年面板，检测当前年份中的所有日期是否禁用
      });
      that.cellRender(li, { year: yearNum, month: 1, date: 1 }, 'year');
      yearNum++;
    });

    lay(elemYM[!isMonthBeforeYear ? 0 : 1])
      .attr('lay-ym', yearNum - 8 + '-' + listYM[1])
      .html(startY + text + ' - ' + (yearNum - 1 + text));
  }

  //生成月列表
  else if (type === 'month') {
    lay.each(new Array(12), function (i) {
      var li = lay.elem('li', {
          'lay-ym': i,
        }),
        ymd = {
          year: listYM[0],
          month: i,
          date: 1,
        };

      i + 1 == listYM[1] && lay(li).addClass(THIS);
      li.innerHTML = lang.months[i];
      ul.appendChild(li);

      /*
      if(listYM[0] < that.firstDate.year){
        ymd.date = options.min.date;
      } else if(listYM[0] >= that.firstDate.year){
        ymd.date = options.max.date;
      }
      */

      that.limit({
        elem: lay(li),
        date: ymd,
        index: index,
        type: type,
        rangeType: index,
        disabledType: 'date', // 月面板，检测当前月份中的所有日期是否禁用
      });
      that.cellRender(li, { year: listYM[0], month: i + 1, date: 1 }, 'month');
    });

    lay(elemYM[!isMonthBeforeYear ? 0 : 1])
      .attr('lay-ym', listYM[0] + '-' + listYM[1])
      .html(listYM[0] + text);
  }

  //生成时间列表
  else if (type === 'time') {
    //检测时分秒状态是否在有效日期时间范围内
    var setTimeStatus = function () {
      lay(ul)
        .find('ol')
        .each(function (i, ol) {
          lay(ol)
            .find('li')
            .each(function (ii, li) {
              that.limit({
                elem: lay(li),
                date: [
                  {
                    hours: ii,
                  },
                  {
                    hours: that[startEnd].hours,
                    minutes: ii,
                  },
                  {
                    hours: that[startEnd].hours,
                    minutes: that[startEnd].minutes,
                    seconds: ii,
                  },
                ][i],
                index: index,
                rangeType: index,
                disabledType: 'time', // 时间面板，分别检测时分秒列表是否禁用
                time: [
                  ['hours'],
                  ['hours', 'minutes'],
                  ['hours', 'minutes', 'seconds'],
                ][i],
              });
            });
        });
      if (!options.range) {
        that.limit({
          elem: lay(that.footer).find(ELEM_CONFIRM),
          date: that[startEnd],
          index: 0,
          time: ['hours', 'minutes', 'seconds'],
          disabledType: 'datetime', // 确认按钮，检测时分秒列表任意一项是否禁用
        });
      }
    };

    var setTimeListVisibility = function () {
      var showHour = options.format.indexOf('H') !== -1;
      var showMinute = options.format.indexOf('m') !== -1;
      var showSecond = options.format.indexOf('s') !== -1;
      var liElem = ul.children;
      var hideCount = 0;

      lay.each([showHour, showMinute, showSecond], function (i, isShow) {
        if (!isShow) {
          liElem[i].className += ' layui-hide';
          hideCount++;
        }
      });
      ul.className += ' laydate-time-list-hide-' + hideCount;
    };

    //初始化时间对象
    if (options.range) {
      if (!that[startEnd]) {
        that[startEnd] = startEnd === 'startTime' ? dateTime : that.endDate;
      }
    } else {
      that[startEnd] = dateTime;
    }

    //生成时分秒
    lay.each([24, 60, 60], function (i, item) {
      var li = lay.elem('li'),
        childUL = ['<p>' + lang.time[i] + '</p><ol>'];
      lay.each(new Array(item), function (ii) {
        childUL.push(
          '<li' +
            (that[startEnd][hms[i]] === ii ? ' class="' + THIS + '"' : '') +
            '>' +
            lay.digit(ii, 2) +
            '</li>',
        );
      });
      li.innerHTML = childUL.join('') + '</ol>';
      ul.appendChild(li);
    });
    setTimeStatus();
    setTimeListVisibility();
  }

  //插入容器
  if (haveList) elemCont.removeChild(haveList);
  elemCont.appendChild(ul);

  //年月面板 - 选择事件
  if (type === 'year' || type === 'month') {
    //显示切换箭头
    lay(that.elemMain[index]).addClass('laydate-ym-show');

    //选中
    lay(ul)
      .find('li')
      .on('click', function () {
        var ym = lay(this).attr('lay-ym') | 0;
        if (lay(this).hasClass(DISABLED)) return;
        if (that.rangeLinked) {
          lay.extend(dateTime, {
            year: type === 'year' ? ym : listYM[0],
            month: type === 'year' ? listYM[1] - 1 : ym,
          });
        } else {
          dateTime[type] = ym;
        }

        //当为年选择器或者年月选择器
        var isYearOrMonth = ['year', 'month'].indexOf(options.type) !== -1;
        var isChangeMonth =
          type === 'year' && ['date', 'datetime'].indexOf(options.type) !== -1;
        if (isYearOrMonth || isChangeMonth) {
          lay(ul)
            .find('.' + THIS)
            .removeClass(THIS);
          lay(this).addClass(THIS);

          //如果为年月选择器，点击了年列表，则切换到月选择器
          if ((options.type === 'month' && type === 'year') || isChangeMonth) {
            that.listYM[index][0] = ym;
            isAlone && ((index ? that.endDate : dateTime).year = ym);
            that.list('month', index);
          }
        } else {
          that.checkDate('limit').calendar(dateTime, index, 'init'); // 重新渲染一下两个面板
          that.closeList();
        }

        if (!options.range) {
          that.limit({
            type: type,
            elem: lay(that.footer).find(ELEM_CONFIRM),
            date: dateTime,
            disabledType: 'datetime', // 按钮，检测日期和时间
          });
        }

        that.setBtnStatus(); //同步按钮可点状态

        //若为月选择器，只有当选择月份时才自动关闭；
        //若为年选择器，选择年份即自动关闭
        //且在范围未开启时
        if (!options.range && options.autoConfirm) {
          if (
            (options.type === 'month' && type === 'month') ||
            (options.type === 'year' && type === 'year')
          ) {
            that.setValue(that.parse()).done().remove();
          }
        }

        that.autoCalendarModel.auto && !that.rangeLinked
          ? that.choose(lay(elemCont).find('td.layui-this'), index)
          : that.endState && that.done(null, 'change');
        lay(that.footer)
          .find('.' + ELEM_TIME_BTN)
          .removeClass(DISABLED);
      });
  } else {
    //时间选择面板 - 选择事件
    var span = lay.elem('span', {
        class: ELEM_TIME_TEXT,
      }),
      //滚动条定位
      scroll = function () {
        lay(ul)
          .find('ol')
          .each(function (i) {
            var ol = this,
              li = lay(ol).find('li');
            ol.scrollTop = 30 * (that[startEnd][hms[i]] - 2);
            if (ol.scrollTop <= 0) {
              li.each(function (ii) {
                if (!lay(this).hasClass(DISABLED)) {
                  ol.scrollTop = 30 * (ii - 2);
                  return true;
                }
              });
            }
          });
      },
      haveSpan = lay(elemHeader[2]).find('.' + ELEM_TIME_TEXT);

    scroll();
    span.innerHTML = options.range
      ? [lang.startTime, lang.endTime][index]
      : lang.selectTime;
    lay(that.elemMain[index]).addClass('laydate-time-show');

    if (haveSpan[0]) haveSpan.remove();
    elemHeader[2].appendChild(span);

    var olElem = lay(ul).find('ol');
    olElem.each(function (i) {
      var ol = this;
      //选择时分秒
      lay(ol)
        .find('li')
        .on('click', function () {
          var value = this.innerHTML | 0;
          if (lay(this).hasClass(DISABLED)) return;

          if (options.range) {
            that[startEnd][hms[i]] = value;
          } else {
            dateTime[hms[i]] = value;
          }
          lay(ol)
            .find('.' + THIS)
            .removeClass(THIS);
          lay(this).addClass(THIS);

          setTimeStatus();
          scroll();
          (that.endDate ||
            options.type === 'time' ||
            options.type === 'datetime') &&
            that.done(null, 'change');

          //同步按钮可点状态
          that.setBtnStatus();
        });
    });

    if (layui.device().mobile) {
      olElem.css({
        overflowY: 'auto',
        touchAction: 'pan-y',
      });
    }
  }

  return that;
};

//记录列表切换后的年月
Class.prototype.listYM = [];

//关闭列表
Class.prototype.closeList = function () {
  var that = this;
  // var options = that.config;

  lay.each(that.elemCont, function (index) {
    lay(this)
      .find('.' + ELEM_LIST)
      .remove();
    lay(that.elemMain[index]).removeClass('laydate-ym-show laydate-time-show');
  });
  lay(that.elem)
    .find('.' + ELEM_TIME_TEXT)
    .remove();
};

//检测结束日期是否超出开始日期
Class.prototype.setBtnStatus = function (tips, start, end) {
  var that = this,
    options = that.config,
    lang = that.i18nMessages,
    isOut,
    elemBtn = lay(that.footer).find(ELEM_CONFIRM),
    timeParams =
      options.type === 'datetime' || options.type === 'time'
        ? ['hours', 'minutes', 'seconds']
        : undefined;
  if (options.range) {
    start = start || (that.rangeLinked ? that.startDate : options.dateTime);
    end = end || that.endDate;
    isOut =
      !that.endState ||
      that.newDate(start).getTime() > that.newDate(end).getTime();

    //如果不在有效日期内，直接禁用按钮，否则比较开始和结束日期
    that.limit({
      date: start,
      disabledType: 'datetime', // 按钮，检测日期和时间
      time: timeParams,
      rangeType: 0,
    }) ||
    that.limit({
      date: end,
      disabledType: 'datetime', // 按钮，检测日期和时间
      time: timeParams,
      rangeType: 1,
    })
      ? elemBtn.addClass(DISABLED)
      : elemBtn[isOut ? 'addClass' : 'removeClass'](DISABLED);

    // 是否异常提示
    if (tips && isOut) {
      that.hint(lang.rangeOrderPrompt);
    }
  }
};

// 转义为规定格式的日期字符
Class.prototype.parse = function (state, date) {
  var that = this;
  var options = that.config;
  var startDate = that.rangeLinked ? that.startDate : options.dateTime;
  var dateTime =
    date ||
    (state == 'end'
      ? lay.extend({}, that.endDate, that.endTime)
      : options.range
        ? lay.extend({}, startDate || options.dateTime, that.startTime)
        : options.dateTime);
  var format = laydate.parse(dateTime, that.format, 1);

  // 返回日期范围字符
  if (options.range && state === undefined) {
    return format + ' ' + that.rangeStr + ' ' + that.parse('end');
  }

  return format;
};

//创建指定日期时间对象
Class.prototype.newDate = function (dateTime) {
  dateTime = dateTime || {};
  return new Date(
    dateTime.year || 1,
    dateTime.month || 0,
    dateTime.date || 1,
    dateTime.hours || 0,
    dateTime.minutes || 0,
    dateTime.seconds || 0,
  );
};

// 获得指定日期时间对象的毫秒数
Class.prototype.getDateTime = function (obj) {
  return this.newDate(obj).getTime();
};

/**
 * 格式化输入框显示值
 * @param {HTMLInputElement} elem HTML input 元素
 * @param {(value: string) => string} displayValueCallback
 */
Class.prototype.formatToDisplay = function (elem, displayValueCallback) {
  var that = this;
  var props = Object.getOwnPropertyDescriptor(
    HTMLInputElement.prototype,
    'value',
  );

  Object.defineProperty(
    elem,
    'value',
    lay.extend({}, props, {
      get: function () {
        return this.getAttribute('lay-date');
      },
      set: function (value) {
        props.set.call(this, displayValueCallback.call(that, value));
        this.setAttribute('lay-date', value);
      },
    }),
  );
};

//赋值
Class.prototype.setValue = function (value) {
  var that = this,
    options = that.config,
    elem = options.elem[0];

  //静态展现则不作默认赋值
  if (options.position === 'static') return that;

  value = value || '';

  //绑定的元素是否为 input
  if (that.isInput(elem)) {
    lay(elem).val(value);
  } else {
    //如果 range 传入了开始和结束的 input 对象，则分别对其赋值
    var rangeElem = that.rangeElem;
    if (rangeElem) {
      if (layui.type(value) !== 'array') {
        value = value.split(' ' + that.rangeStr + ' ');
      }
      rangeElem[0].val(value[0] || '');
      rangeElem[1].val(value[1] || '');
    } else {
      if (lay(elem).find('*').length === 0) {
        var displayValue =
          typeof options.formatToDisplay === 'function'
            ? options.formatToDisplay(value)
            : value;
        lay(elem).html(displayValue);
      }
      lay(elem).attr('lay-date', value);
    }
  }

  return that;
};

//预览
Class.prototype.preview = function () {
  var that = this,
    options = that.config;

  if (!options.isPreview) return;

  var elemPreview = lay(that.elem).find('.' + ELEM_PREVIEW),
    value = options.range
      ? (that.rangeLinked ? that.endState : that.endDate)
        ? that.parse()
        : ''
      : that.parse();

  // 显示预览
  elemPreview.html(value);

  // 预览颜色渐变
  var oldValue = elemPreview.html();
  if (oldValue) {
    var color =
      layui.type(options.theme) === 'array' ? options.theme[0] : options.theme;
    elemPreview.css({
      color: /^#/.test(String(color)) ? color : '#16b777',
    });

    setTimeout(function () {
      elemPreview.css({
        color: '#777',
      });
    }, 300);
  }
};

// 附加的渲染处理，在 ready 和 change 的时候调用
Class.prototype.renderAdditional = function () {
  var that = this;
  var options = that.config;

  // 处理全面板
  if (options.fullPanel) {
    that.list('time', 0);
  }
};

// 标记范围内的日期
Class.prototype.stampRange = function () {
  var that = this,
    options = that.config,
    startTime = that.rangeLinked ? that.startDate : options.dateTime,
    endTime,
    tds = lay(that.elem).find('td');

  if (options.range && !that.endState)
    lay(that.footer).find(ELEM_CONFIRM).addClass(DISABLED);
  // if(!that.endState) return;

  startTime =
    startTime &&
    that
      .newDate({
        year: startTime.year,
        month: startTime.month,
        date: startTime.date,
      })
      .getTime();

  endTime =
    that.endState &&
    that.endDate &&
    that
      .newDate({
        year: that.endDate.year,
        month: that.endDate.month,
        date: that.endDate.date,
      })
      .getTime();

  // if(startTime > endTime) return that.hint(TIPS_OUT);

  lay.each(tds, function (i, item) {
    var ymd = lay(item).attr('lay-ymd').split('-');
    var thisTime = that
      .newDate({
        year: ymd[0],
        month: ymd[1] - 1,
        date: ymd[2],
      })
      .getTime();

    // 标记当天
    if (options.rangeLinked && !that.startDate) {
      if (thisTime === that.newDate(that.systemDate()).getTime()) {
        lay(item).addClass(
          lay(item).hasClass(ELEM_PREV) || lay(item).hasClass(ELEM_NEXT)
            ? ''
            : ELEM_DAY_NOW,
        );
      }
    }

    /*
     * 标注区间
     */

    lay(item).removeClass(ELEM_SELECTED + ' ' + THIS);

    if (thisTime === startTime || thisTime === endTime) {
      (that.rangeLinked ||
        (!that.rangeLinked &&
          (i < 42 ? thisTime === startTime : thisTime === endTime))) &&
        lay(item).addClass(
          lay(item).hasClass(ELEM_PREV) || lay(item).hasClass(ELEM_NEXT)
            ? ELEM_SELECTED
            : THIS,
        );
    }
    if (thisTime > startTime && thisTime < endTime) {
      lay(item).addClass(ELEM_SELECTED);
    }
  });
};

// 执行 done/change 回调
Class.prototype.done = function (param, type) {
  var that = this;
  var options = that.config;
  var start = lay.extend(
    {},
    lay.extend(
      that.rangeLinked ? that.startDate : options.dateTime,
      that.startTime,
    ),
  );
  var end = lay.extend({}, lay.extend(that.endDate, that.endTime));

  lay.each([start, end], function (i, item) {
    if (!('month' in item)) return;
    lay.extend(item, {
      month: item.month + 1,
    });
  });

  that.preview();

  param = param || [that.parse(), start, end];
  type === 'change' && that.renderAdditional();
  typeof options[type || 'done'] === 'function' &&
    options[type || 'done'].apply(options, param);

  return that;
};

/**
 * 判断日期是否存在面板，用于处理日期范围选择的一些边缘情况
 * @param {object} datetime 日期时间对象
 * @param {number} index - 面板索引
 * @returns
 */
Class.prototype.checkPanelDate = function (datetime, index) {
  var that = this;
  var options = that.config;
  // 年月范围选择不需要处理
  if (options.type !== 'date' && options.type !== 'datetime') return;

  var startPanel = index === 0;
  var month = datetime.month + 1; // 点击的日期所在月份
  var panelMonth = that.panelYM[index].month + 1; // 当前面板头部月份

  // 边缘日期的处理
  var firstRenderIndex = that.endState
    ? // 二次点击（一般为结束日期）任意一侧面板时：
      // 1. 左侧面板中，点击的月份属于下一个月时，应渲染右侧面板而不是左侧面板;
      // 2. 左侧面板中，点击的月份属于上一个月时，应将两个面板都重新渲染(等效点击 prevMonth);
      // 3. 右侧面板同理。
      (function () {
        return (startPanel && month > panelMonth) ||
          (!startPanel && month < panelMonth)
          ? 1 - index
          : index;
      })()
    : // 初次点击（一般为开始日期）任意一侧面板时：
      // 1. 让该面板自行切换，以保持日期的「选中状态」在该侧；
      // 2. 另一侧面板则根据点击的面板进行响应式切换，以保持左右面板始终为连续月份。
      index;

  // 为简化实现，只要点击的月份不等于当前面板顶部显示的月份时，就重新渲染两侧面板
  return {
    needFullRender: month !== panelMonth,
    index: firstRenderIndex,
  };
};

//选择日期
Class.prototype.choose = function (td, index) {
  if (td.hasClass(DISABLED)) return;

  var that = this,
    options = that.config,
    panelIndex = index; // 记录点击的是哪一个面板的

  if (that.rangeLinked) {
    if (that.endState || !that.startDate) {
      // 重新选择或者第一次选择
      index = 0;
      that.endState = false;
    } else {
      index = 1;
      that.endState = true;
    }
  }

  var dateTime = that.thisDateTime(index),
    // tds = lay(that.elem).find('td'),
    YMD = td.attr('lay-ymd').split('-');

  YMD = {
    year: YMD[0] | 0,
    month: (YMD[1] | 0) - 1,
    date: YMD[2] | 0,
  };

  lay.extend(dateTime, YMD); //同步 dateTime

  //范围选择
  if (options.range) {
    //补充时分秒
    lay.each(['startTime', 'endTime'], function (i, item) {
      that[item] = that[item] || {
        hours: i ? 23 : 0,
        minutes: i ? 59 : 0,
        seconds: i ? 59 : 0,
      };
      if (index === i) {
        // 判断选择之后的是否在范围内，超出则需要调整时分秒
        if (
          that.getDateTime(lay.extend({}, dateTime, that[item])) <
          that.getDateTime(options.min)
        ) {
          that[item] = {
            hours: options.min.hours,
            minutes: options.min.minutes,
            seconds: options.min.seconds,
          };
          lay.extend(dateTime, that[item]);
        } else if (
          that.getDateTime(lay.extend({}, dateTime, that[item])) >
          that.getDateTime(options.max)
        ) {
          that[item] = {
            hours: options.max.hours,
            minutes: options.max.minutes,
            seconds: options.max.seconds,
          };
          lay.extend(dateTime, that[item]);
        }
      }
    });
    if (!index) {
      that.startDate = lay.extend({}, dateTime); // 同步startDate
    }
    // 校验另外一个日期是否在有效的范围内
    // 此处为范围选择的日期面板点击选中处理，所以 disabledType 为 date
    if (
      that.endState &&
      !that.limit({
        date: that.rangeLinked ? that.startDate : that.thisDateTime(1 - index),
        disabledType: 'date',
      })
    ) {
      // 根据选择之后判断是否需要切换模式
      var isChange;
      if (that.endState && that.autoCalendarModel.auto) {
        isChange = that.autoCalendarModel();
      }
      // 判断是否反选
      var needSwapDate =
        (isChange || (that.rangeLinked && that.endState)) &&
        that.newDate(that.startDate) > that.newDate(that.endDate);
      if (needSwapDate) {
        var isSameDate =
          that.startDate.year === that.endDate.year &&
          that.startDate.month === that.endDate.month &&
          that.startDate.date === that.endDate.date;
        var startDate;
        // 如果是同一天并且出现了反选证明是时分秒出现开始时间大于结束时间的现象
        if (isSameDate) {
          startDate = that.startTime;
          that.startTime = that.endTime;
          that.endTime = startDate;
        }
        // 当出现反向选择时（即“后点击”的日期比“先点击”的日期小），重新提取区间
        startDate = that.startDate;
        that.startDate = lay.extend({}, that.endDate, that.startTime);
        options.dateTime = lay.extend({}, that.startDate);
        that.endDate = lay.extend({}, startDate, that.endTime);
      }
      isChange && (options.dateTime = lay.extend({}, that.startDate));
    }
    if (that.rangeLinked) {
      // 处理日期范围选择的一些边缘情况
      var checkState = that.checkPanelDate(dateTime, panelIndex);
      var dateTimeTemp = lay.extend({}, dateTime);
      var renderMode =
        isChange || (checkState && checkState.needFullRender) ? 'init' : null;
      var panelIdx = checkState ? checkState.index : panelIndex;
      that.calendar(dateTimeTemp, panelIdx, renderMode);
    } else {
      that.calendar(null, index, isChange ? 'init' : null);
    }
    that.endState && that.done(null, 'change');
  } else if (options.position === 'static') {
    //直接嵌套的选中
    that.calendar().done().done(null, 'change'); //同时执行 done 和 change 回调
  } else if (options.type === 'date') {
    options.autoConfirm
      ? that.setValue(that.parse()).done().remove()
      : that.calendar().done(null, 'change');
  } else if (options.type === 'datetime') {
    that.calendar().done(null, 'change');
  }
};

//底部按钮
Class.prototype.tool = function (btn, type) {
  var that = this,
    options = that.config,
    lang = that.i18nMessages,
    dateTime = options.dateTime,
    isStatic = options.position === 'static',
    active = {
      //选择时间
      datetime: function () {
        if (lay(btn).hasClass(DISABLED)) return;
        that.list('time', 0);
        options.range && that.list('time', 1);
        lay(btn).attr('lay-type', 'date').html(that.i18nMessages.selectDate);
      },

      //选择日期
      date: function () {
        that.closeList();
        lay(btn)
          .attr('lay-type', 'datetime')
          .html(that.i18nMessages.selectTime);
      },

      //清空、重置
      clear: function () {
        isStatic && (lay.extend(dateTime, that.firstDate), that.calendar());
        options.range &&
          (delete options.dateTime,
          delete that.endDate,
          delete that.startTime,
          delete that.endTime);
        that.setValue('');
        that.done(null, 'onClear').done(['', {}, {}]).remove();
      },

      // 现在
      now: function () {
        var thisDate = new Date();

        // 当前系统时间未在 min/max 范围内，则不可点击
        if (lay(btn).hasClass(DISABLED)) {
          return that.hint(lang.tools.now + ', ' + lang.invalidDatePrompt);
        }

        lay.extend(dateTime, that.systemDate(), {
          hours: thisDate.getHours(),
          minutes: thisDate.getMinutes(),
          seconds: thisDate.getSeconds(),
        });

        that.setValue(that.parse());
        isStatic && that.calendar();
        that.done(null, 'onNow').done().remove();
      },

      //确定
      confirm: function () {
        if (options.range) {
          if (lay(btn).hasClass(DISABLED)) {
            var isTimeout =
              options.type === 'time'
                ? that.startTime &&
                  that.endTime &&
                  that.newDate(that.startTime) > that.newDate(that.endTime)
                : that.startDate &&
                  that.endDate &&
                  that.newDate(
                    lay.extend({}, that.startDate, that.startTime || {}),
                  ) >
                    that.newDate(
                      lay.extend({}, that.endDate, that.endTime || {}),
                    );

            return that.hint(
              isTimeout ? lang.rangeOrderPrompt : lang.invalidDatePrompt,
            );
          }
        } else {
          if (lay(btn).hasClass(DISABLED))
            return that.hint(lang.invalidDatePrompt);
        }

        that.setValue(that.parse());
        that.done(null, 'onConfirm').done().remove();
      },
    };
  active[type] && active[type]();
};

//统一切换处理
Class.prototype.change = function (index) {
  var that = this,
    options = that.config,
    dateTime = that.thisDateTime(index),
    isAlone =
      options.range && (options.type === 'year' || options.type === 'month'),
    elemCont = that.elemCont[index || 0],
    listYM = that.listYM[index],
    addSubYear = function (type) {
      var isYear = lay(elemCont).find('.laydate-year-list')[0],
        isMonth = lay(elemCont).find('.laydate-month-list')[0];

      //切换年列表
      if (isYear) {
        listYM[0] = type ? listYM[0] - 15 : listYM[0] + 15;
        that.list('year', index);
      }

      if (isMonth) {
        //切换月面板中的年
        type ? listYM[0]-- : listYM[0]++;
        that.list('month', index);
      }

      if (isYear || isMonth) {
        lay.extend(dateTime, {
          year: listYM[0],
        });
        if (isAlone) dateTime.year = listYM[0];
        options.range || that.done(null, 'change');
        options.range ||
          that.limit({
            elem: lay(that.footer).find(ELEM_CONFIRM),
            date: {
              year: listYM[0],
              month: isYear ? 0 : listYM[1] - 1,
            },
            disabledType: 'datetime', // 按钮，检测日期和时间
          });
      }

      that.setBtnStatus();
      return isYear || isMonth;
    };

  return {
    prevYear: function () {
      if (addSubYear('sub')) return;
      if (that.rangeLinked) {
        options.dateTime.year--;
        that.checkDate('limit').calendar(null, null, 'init');
      } else {
        dateTime.year--;
        that.checkDate('limit').calendar(null, index);
        // 面板自动切换的模式下重新判定是否发生模式转换等细节处理
        that.autoCalendarModel.auto
          ? that.choose(lay(elemCont).find('td.layui-this'), index)
          : that.done(null, 'change');
      }
    },
    prevMonth: function () {
      var YM;
      // rangeLinked 模式非实时选择日期，不需要同步 options.dateTime，应根据面板显示日期切换
      if (that.rangeLinked) {
        var panelYM = that.panelYM[0];
        var dateTimeTemp;
        YM = that.getAsYM(panelYM.year, panelYM.month, 'sub');
        dateTimeTemp = lay.extend({}, options.dateTime, that.panelYM[0], {
          year: YM[0],
          month: YM[1],
        });
        that.checkDate('limit').calendar(dateTimeTemp, null, 'init');
      } else {
        YM = that.getAsYM(dateTime.year, dateTime.month, 'sub');
        lay.extend(dateTime, {
          year: YM[0],
          month: YM[1],
        });

        that.checkDate('limit').calendar(null, null, 'init');
        that.autoCalendarModel.auto
          ? that.choose(lay(elemCont).find('td.layui-this'), index)
          : that.done(null, 'change');
      }
    },
    nextMonth: function () {
      var YM;

      if (that.rangeLinked) {
        var panelYM = that.panelYM[0];
        YM = that.getAsYM(panelYM.year, panelYM.month);
        var dateTimeTemp = lay.extend({}, options.dateTime, that.panelYM[0], {
          year: YM[0],
          month: YM[1],
        });
        that.checkDate('limit').calendar(dateTimeTemp, null, 'init');
      } else {
        YM = that.getAsYM(dateTime.year, dateTime.month);

        lay.extend(dateTime, {
          year: YM[0],
          month: YM[1],
        });

        that.checkDate('limit').calendar(null, null, 'init');
        that.autoCalendarModel.auto
          ? that.choose(lay(elemCont).find('td.layui-this'), index)
          : that.done(null, 'change');
      }
    },
    nextYear: function () {
      if (addSubYear()) return;
      if (that.rangeLinked) {
        options.dateTime.year++;
        that.checkDate('limit').calendar(null, 0, 'init');
      } else {
        dateTime.year++;
        that.checkDate('limit').calendar(null, index);
        that.autoCalendarModel.auto
          ? that.choose(lay(elemCont).find('td.layui-this'), index)
          : that.done(null, 'change');
      }
    },
  };
};

// 日期切换事件
Class.prototype.changeEvent = function () {
  var that = this;
  // var options = that.config;

  //日期选择事件
  lay(that.elem)
    .on('click', function (e) {
      lay.stope(e);
    })
    .on('mousedown', function (e) {
      lay.stope(e);
    });

  //年月切换
  lay.each(that.elemHeader, function (i, header) {
    //上一年
    lay(header[0]).on('click', function () {
      that.change(i).prevYear();
    });

    //上一月
    lay(header[1]).on('click', function () {
      that.change(i).prevMonth();
    });

    //选择年月
    lay(header[2])
      .find('span')
      .on('click', function () {
        var othis = lay(this),
          layYM = othis.attr('lay-ym'),
          layType = othis.attr('lay-type');

        if (!layYM) return;

        layYM = layYM.split('-');

        that.listYM[i] = [layYM[0] | 0, layYM[1] | 0];
        that.list(layType, i);
        lay(that.footer)
          .find('.' + ELEM_TIME_BTN)
          .addClass(DISABLED);
      });

    //下一月
    lay(header[3]).on('click', function () {
      that.change(i).nextMonth();
    });

    //下一年
    lay(header[4]).on('click', function () {
      that.change(i).nextYear();
    });
  });

  //点击日期
  lay.each(that.table, function (i, table) {
    var tds = lay(table).find('td');
    tds.on('click', function () {
      that.choose(lay(this), i);
    });
  });

  //点击底部按钮
  lay(that.footer)
    .find('span')
    .on('click', function () {
      var type = lay(this).attr('lay-type');
      that.tool(this, type);
    });
};

//是否输入框
Class.prototype.isInput = function (elem) {
  return (
    /input|textarea/.test(elem.tagName.toLocaleLowerCase()) ||
    /INPUT|TEXTAREA/.test(elem.tagName)
  );
};

//绑定的元素事件处理
Class.prototype.events = function () {
  var that = this;
  var options = that.config;

  if (!options.elem[0] || options.elem[0].eventHandler) return;

  var showEvent = function () {
    // 已经打开的面板避免重新渲染
    if (laydate.thisId === options.id) return;
    that.render();
  };

  //绑定呼出控件事件
  options.elem.on(options.trigger, showEvent);
  options.elem[0].eventHandler = true;
  options.eventElem.on(options.trigger, showEvent);

  // 元素解绑
  that.unbind = function () {
    that.remove();
    options.elem.off(options.trigger, showEvent);
    options.elem.removeAttr('lay-key');
    options.elem.removeAttr(MOD_ID);
    options.elem[0].eventHandler = false;
    options.eventElem.off(options.trigger, showEvent);
    options.eventElem.removeAttr('lay-key');
    delete thisModule.that[options.id];
  };
};

// 绑定关闭控件事件
lay(document)
  .on('mousedown', function (e) {
    if (!laydate.thisId) return;
    var that = thisModule.getThis(laydate.thisId);
    if (!that) return;

    var options = that.config;

    if (
      e.target === options.elem[0] ||
      e.target === options.eventElem[0] ||
      e.target === lay(options.closeStop)[0] ||
      (options.elem[0] && options.elem[0].contains(e.target))
    )
      return;

    that.remove();
  })
  .on('keydown', function (e) {
    if (!laydate.thisId) return;
    var that = thisModule.getThis(laydate.thisId);
    if (!that) return;

    // 回车触发确认
    if (that.config.position === 'static') return;
    if (e.keyCode === 13) {
      if (lay('#' + that.elemID)[0] && that.elemID === Class.thisElemDate) {
        e.preventDefault();
        lay(that.footer).find(ELEM_CONFIRM)[0].click();
      }
    }
  });

//自适应定位
lay(window).on('resize', function () {
  if (!laydate.thisId) return;
  var that = thisModule.getThis(laydate.thisId);
  if (!that) return;

  if (!that.elem || !lay(ELEM)[0]) {
    return false;
  }

  that.position();
});

// 记录所有实例
thisModule.that = {}; //记录所有实例对象

// 获取当前实例对象
thisModule.getThis = function (id) {
  var that = thisModule.that[id];
  if (!that) {
    layui
      .hint()
      .error(
        id
          ? MOD_NAME + " instance with ID '" + id + "' not found"
          : 'ID argument required',
      );
  }
  return that;
};

// 渲染 - 核心接口
laydate.render = function (options) {
  var inst = new Class(options);
  return thisModule.call(inst);
};

// 重载
laydate.reload = function (id, options) {
  var that = thisModule.getThis(id);
  if (!that) return;
  return that.reload(options);
};

// 获取对应 ID 的实例
laydate.getInst = function (id) {
  var that = thisModule.getThis(id);
  if (that) {
    return that.inst;
  }
};

// 面板提示
laydate.hint = function (id, opts) {
  var that = thisModule.getThis(id);
  if (!that) return;
  return that.hint(opts);
};

// 解绑实例
laydate.unbind = function (id) {
  var that = thisModule.getThis(id);
  if (!that) return;
  return that.unbind();
};

// 关闭日期面板
laydate.close = function (id) {
  var that = thisModule.getThis(id || laydate.thisId);
  if (!that) return;
  return that.remove();
};

// 将指定对象转化为日期值
laydate.parse = function (dateTime, format, one) {
  dateTime = dateTime || {};

  //如果 format 是字符型，则转换为数组格式
  if (typeof format === 'string') {
    format = thisModule.formatArr(format);
  }

  format = (format || []).concat();

  //转义为规定格式
  lay.each(format, function (i, item) {
    if (/yyyy|y/.test(item)) {
      //年
      format[i] = lay.digit(dateTime.year, item.length);
    } else if (/MM|M/.test(item)) {
      //月
      format[i] = lay.digit(dateTime.month + (one || 0), item.length);
    } else if (/dd|d/.test(item)) {
      //日
      format[i] = lay.digit(dateTime.date, item.length);
    } else if (/HH|H/.test(item)) {
      //时
      format[i] = lay.digit(dateTime.hours, item.length);
    } else if (/mm|m/.test(item)) {
      //分
      format[i] = lay.digit(dateTime.minutes, item.length);
    } else if (/ss|s/.test(item)) {
      //秒
      format[i] = lay.digit(dateTime.seconds, item.length);
    }
  });

  return format.join('');
};

// 得到某月的最后一天
laydate.getEndDate = function (month, year) {
  var thisDate = new Date();
  //设置日期为下个月的第一天
  thisDate.setFullYear(
    year || thisDate.getFullYear(),
    month || thisDate.getMonth() + 1,
    1,
  );
  //减去一天，得到当前月最后一天
  return new Date(thisDate.getTime() - 1000 * 60 * 60 * 24).getDate();
};

export { laydate };
